package upload

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gookit/event"
	"github.com/qiniu/api.v7/v7/auth"
	"github.com/qiniu/api.v7/v7/storage"
	"github.com/vueadmin/global"
	"github.com/vueadmin/utils"
	"github.com/vueadmin/utils/common/result"
	"github.com/vueadmin/utils/conv"
	"github.com/vueadmin/utils/response"
	"io/ioutil"
	"net/http"
	"path"
	"strings"
	"time"
)

var Upload = new(upload)

type upload struct{}

func (p upload) Upload(c *gin.Context) {
	rename := c.DefaultPostForm("rename", "1")
	upload_config_id := c.PostForm("upload_config_id")
	file, err := c.FormFile("file")
	if err != nil {
		panic(err)
	}
	if file.Size > conv.Int64(c.MustGet("filesize"))*1024*1024 {
		response.Fail(c, "文件大小验证失败")
		return
	}
	fileExt := path.Ext(file.Filename)
	if !utils.FileValid(fileExt, c.MustGet("filetype").(string)) {
		response.Fail(c, "文件后缀验证失败")
		return
	}

	fileHandle, _ := file.Open()
	defer fileHandle.Close()
	body, err2 := ioutil.ReadAll(fileHandle)
	if err != nil {
		panic(err2)
	}
	hash := utils.Md5(string(body))

	var filepath string

	//先查询库看相同的图片是否有已存在的路径，有直接返回即可
	if global.CONF.CheckFileStatus {
		if err3 := global.DB.Table(global.CONF.Db.Prefix+"file").Where("hash", hash).Limit(1).Pluck("filepath", &filepath).Error; err != nil {
			response.Err(c, err3)
			return
		}
		if filepath != "" {
			response.Success(c, "上传成功", result.M{
				"filestatus": true,
				"data":       filepath,
			})
			return
		}
	}
	//判断文件是否重命名
	var fileName string
	if rename == "1" {
		fileName = fmt.Sprintf("%s/%s%s", utils.GetSavePath(c), hash, fileExt)
	} else {
		fileName = fmt.Sprintf("%s/%s", utils.GetSavePath(c), file.Filename)
	}
	//客户端上传返回需要的参数
	if global.CONF.Disks != "local" && global.CONF.OssType == "client" && conv.IsEmpty(c.PostForm("edit")) {
		p.OssClientUpload(c, fileName)
		return
	}

	switch global.CONF.Disks {
	case "local":
		fileName = Local.Upload(file, fileName, c)
	case "qiniu":
		fileName = QiniuOss.Upload(file, fileName)
	case "ali":
		fileName = AliyunOss.Upload(file, fileName)
	case "tencent":
		fileName = TencentOss.Upload(file, fileName)
	default:
		fileName = Local.Upload(file, fileName, c)
	}

	//本地上传需要拼接url，oss已经拼接好了
	if global.CONF.Disks == "local" {
		filepath = c.MustGet("domain").(string) + fileName
	} else {
		filepath = fileName
	}
	//分发事件
	event.MustFire("afterUploadEvent", event.M{
		"filepath":         filepath,
		"upload_config_id": upload_config_id,
		"hash":             hash,
	})
	response.Success(c, "上传成功", filepath)
}

// Oss客户端上传
func (p upload) OssClientUpload(c *gin.Context, fileName string) {
	fileName = strings.TrimLeft(fileName, "/")
	switch global.CONF.Disks {
	case "qiniu":
		response.Success(c, "success", result.M{
			"serverurl": global.CONF.QiniuOss.ClientUploadurl,
			"domain":    global.CONF.QiniuOss.Domain,
			"token":     p.GetQiniuToken(),
			"key":       fileName,
			"type":      "qiniu",
		})
	case "tencent":
		response.Success(c, "success", result.M{
			"SecretId":  global.CONF.TencentCOS.SecretID,
			"SecretKey": global.CONF.TencentCOS.SecretKey,
			"Bucket":    global.CONF.TencentCOS.Bucket,
			"Schema":    global.CONF.TencentCOS.Schema,
			"Region":    global.CONF.TencentCOS.Region,
			"key":       fileName,
			"type":      "tencent",
		})
	case "ali":
		options := make(map[string]interface{})
		expire := 30 // 设置该policy超时时间是30s。即这个policy过了这个有效时间，将不能访问。
		now := time.Now()
		end := now.Add(time.Second * time.Duration(expire))
		options["expiration"] = end.UTC().Format("2006-01-02T15:04:05Z") // 授权过期时间

		conditions := make([]interface{}, 0)
		conditions = append(conditions, map[string]interface{}{"bucket": global.CONF.AliyunOSS.BucketName})

		callbackParam := map[string]interface{}{
			"callbackUrl":      global.CONF.AliyunOSS.Callback,
			"callbackBody":     "${object}",
			"callbackBodyType": "application/x-www-form-urlencoded",
		}
		callbackString, _ := json.Marshal(callbackParam)
		base64CallbackBody := base64.StdEncoding.EncodeToString(callbackString)

		contentLengthRange := make([]interface{}, 0)
		contentLengthRange = append(contentLengthRange, "content-length-range")
		contentLengthRange = append(contentLengthRange, 0)
		contentLengthRange = append(contentLengthRange, 2048*1024*1024)
		conditions = append(conditions, contentLengthRange)

		options["conditions"] = conditions
		policyBytes, _ := json.Marshal(options)
		policy := base64.StdEncoding.EncodeToString(policyBytes)

		mac := hmac.New(sha1.New, []byte(global.CONF.AliyunOSS.AccessKeySecret))
		mac.Write([]byte(policy))
		sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))

		response.Success(c, "success", result.M{
			"sign":           sign,
			"policy":         policy,
			"callback":       base64CallbackBody,
			"OSSAccessKeyId": global.CONF.AliyunOSS.AccessKeyId,
			"serverurl":      utils.GetEndPoint(global.CONF.AliyunOSS.Endpoint),
			"key":            fileName,
			"type":           "ali",
		})
	}
}

// 获取七牛云上传客户端token
func (p upload) GetQiniuToken() string {
	mac := auth.New(global.CONF.QiniuOss.AccessKey, global.CONF.QiniuOss.SecretKey)
	putPolicy := storage.PutPolicy{
		Scope: global.CONF.QiniuOss.Bucket,
	}
	putPolicy.Expires = uint64(time.Now().Add(time.Hour).Unix())
	return putPolicy.UploadToken(mac)
}

// 阿里云客户端上传回调地址
func (p upload) AliOssCallBack(c *gin.Context) {
	body := c.Request.FormValue("body")
	if conv.IsEmpty(body) {
		response.Fail(c, "回调数据不能为空")
		return
	}
	url := utils.GetEndPoint(global.CONF.AliyunOSS.Endpoint) + "/" + strings.ReplaceAll(body, "%2F", "/")
	response := map[string]interface{}{
		"code": 1,
		"data": url,
	}
	c.JSON(http.StatusOK, response)
}
